package org.dandsoft.blackbox.snn;

import org.dandsoft.blackbox.*;
import org.dandsoft.blackbox.logger.Logger;
import org.dandsoft.blackbox.snn.NeuronFieldMonitor;
import org.dandsoft.blackbox.util.list.EntryList;
import org.dandsoft.blackbox.util.list.Entry;

import java.util.Iterator;

/**
 * User: dvilyuzhanin
 * Date: 19.01.2009 18:37:16
 */
public class NeuronField {

    private NeuronFieldMonitor loggerNeuron = new NeuronFieldMonitor();
    private NeuronStructure structure;
    private InputSignal input;
    private OutputSignal output;
    private TaskType currentTaskType = TaskType.Activate_Synapses;
    private final Object lock = new Object();
    private int threadCounter = 0;
    private Time currentTime = new Time(0);
    private Enviroment enviroment = new EnviromentImpl();
    private EntryList<Synapse> usedSynapsesList = new EntryList<Synapse>();
    private EntryList<Synapse> activeSynapsesList = new EntryList<Synapse>();
    private EntryList<Neuron> usedNeuronList = new EntryList<Neuron>();

    public boolean work(Time time) throws SnnException {
        if (startWork(time)) {
            try {
                if (isActivateSynapses()) {
                    loggerNeuron.activateSynapses(time);
                    activateSynapses();
                    return true;
                } else if (isActivateNeurons()) {
                    loggerNeuron.activateNeurons(time);
                    activateNeurons(time);
                    return true;
                } else {
                    return false;
                }
            } finally {
                finishWork(time);
            }
        } else {
            return false;
        }
    }


    protected void activateSynapses() throws SnnException {

        Synapse synapse;
        while ((synapse = usedSynapsesList.nextObject()) != null) {
            synapse.activate();
        }
        activateRemoteSynapses();
    }

    protected void activateRemoteSynapses() throws SnnException {
        Signal signal;
        try {
            while ((signal = input.read()) != null) {
                Synapse synapse = structure.getSynapse(signal.getId());
                if (synapse != null) {
                    synapse.setEnviroment(enviroment);
                    synapse.setNewValue(signal.getValue());
                    synapse.activate();
                }
            }
        } catch (ChannelException e) {
            throw new SnnException(e);
        }
    }


    protected void activateNeurons(Time time) throws SnnException {
        Neuron neuron;
        while ((neuron = usedNeuronList.nextObject()) != null) {
            neuron.activate();
        }
    }

    private boolean startWork(Time time) throws SnnException {
        synchronized (lock) {
            if (!currentTime.equals(time)) {
                if (threadCounter == 0) {
                    try {
                        if (output != null) {
                            if (!output.isFrameStarted()) {
                                output.startFrame();
                            }
                        }
                        if (input != null) {
                            if (!input.isFrame()) {
                                input.next();
                            }
                        }
                    } catch (ChannelException e) {
                        loggerNeuron.startNewTimeUnitError(time, e.toString());
                        throw new SnnException(e);
                    }
                    loggerNeuron.startNewTimeUnit(time);
                    currentTime = time;
                } else {
                    return false;
                }
            }
            loggerNeuron.startWork(time);
            threadCounter++;
            return true;
        }
    }

    private void finishWork(Time time) throws SnnException {
        synchronized (lock) {
            loggerNeuron.finishWork(time);
            threadCounter--;
            if (threadCounter == 0) {
                loggerNeuron.finishTimeUnit(time);
                if (TaskType.Activate_Neurons == currentTaskType) {
                    currentTaskType = TaskType.Activate_Synapses;
                } else if (TaskType.Activate_Synapses == currentTaskType) {
                    currentTaskType = TaskType.Activate_Neurons;
                }

                if (output != null) {
                    try {
                        output.finishFrame();
                    } catch (ChannelException e) {
                        e.printStackTrace();
                    }
                }

                if (input != null) {
                    try {
                        input.finishFrame();
                    } catch (ChannelException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private boolean isActivateSynapses() {
        return TaskType.Activate_Synapses == currentTaskType;
    }

    private boolean isActivateNeurons() {
        return TaskType.Activate_Neurons == currentTaskType;
    }


    //==========================================================
/**
 * Neurons and synapses enviroment
 */
class EnviromentImpl implements Enviroment {

        public void addToUsedSynapsesList(Entry<Synapse> entry) throws SnnException {
            usedSynapsesList.add(entry);
        }

        public void addToUsedNeuronsList(Entry<Neuron> entry) throws SnnException {
            usedNeuronList.add(entry);
        }

        public Iterator<Synapse> getSynapsesBySource(ObjectId id) throws SnnException {
            return new SynapsesEnviromentSetIterator(structure.getSynapsesBySource(id));
        }

        public Neuron getNeuron(ObjectId neuron) throws SnnException {
            return structure.getNeuron(neuron);
        }

        public void output(ObjectId remoteId, Value value) throws SnnException {
            try {
                output.output(remoteId, value);
            } catch (ChannelException e) {
                throw new SnnException(e);
            }
        }
    }

    /**
     * Proxy for initialize synapses enviroment
     */
    class SynapsesEnviromentSetIterator implements Iterator<Synapse> {
        private Iterator<Synapse> iterator;

        SynapsesEnviromentSetIterator(Iterator<Synapse> iterator) {
            this.iterator = iterator;
        }

        public boolean hasNext() {
            return iterator.hasNext();
        }

        public Synapse next() {
            Synapse synapse = iterator.next();
            if (synapse != null) {
                synapse.setEnviroment(enviroment);
            }
            return synapse;
        }

        public void remove() {

        }
    }

    //==========================================================
    public TaskType getCurrentTaskType() {
        return currentTaskType;
    }

    public void setCurrentTaskType(TaskType currentTaskType) {
        this.currentTaskType = currentTaskType;
    }

    public Time getCurrentTime() {
        synchronized (lock) {
            return currentTime;
        }
    }

    public int getThreadCounter() {
        synchronized (lock) {
            return threadCounter;
        }
    }

    public EntryList<Synapse> getUsedSynapsesList() {
        return usedSynapsesList;
    }

    public EntryList<Synapse> getActiveSynapsesList() {
        return activeSynapsesList;
    }

    public InputSignal getInput() {
        return input;
    }

    public void setInput(InputSignal input) {
        this.input = input;
    }

    public OutputSignal getOutput() {
        return output;
    }

    public void setOutput(OutputSignal output) {
        this.output = output;
    }

    public NeuronStructure getStructure() {
        return structure;
    }

    public void setStructure(NeuronStructure structure) {
        this.structure = structure;
    }

    public EntryList<Neuron> getUsedNeuronList() {
        return usedNeuronList;
    }

    public Logger getLogger() {
        return loggerNeuron.getLogger();
    }

    public void setLogger(Logger logger) {
        loggerNeuron.setLogger(logger);
    }
}
